#!/usr/bin/env python
"""
SoulServer - A Soulseek server implemented in Python
"""
# Copyright (C) 2009 daelstorm. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
import select, socket, signal
import struct
import sys, os, time, zlib
import cPickle
import types
import gettext, getopt
VERSION = 0.1
BUFSIZ = 1024
LOGLEVEL = 1
tr_cache = {}
marshall = cPickle.dumps
unmarshall = cPickle.loads
def _(s):
	global tr_cache
	global langTranslation
	# Don't translate empty strings
	# Poedit uses empty strings as metadata
	if s == "": return s
	if s not in tr_cache:
		tr_cache[s] = gettext.gettext(s)
	return tr_cache[s]


class logger:
	def __init__(self):
		self.history = []
		
	def addChat(self, username=None, message=None, room=None):
		if room:
			self.log("Room: [%s] %s said: %s" % (room, username, message))
		else:
			self.log("Private chat: %s said: %s" % (username, message))
	def addLine(self, message):
		if LOGLEVEL <= 1:
			self.log(message)
	def addError(self, message):
		if LOGLEVEL <= 2:
			self.log("Error: %s" % message)
	def addParsingError(self, message):
		self.log("Parsing Error: %s" % message)
	def log(self, message):
		print time.strftime("%H:%M:%S", time.localtime(time.time())), message
		
log = logger()
	
try:
	import GeoIP
	geoip = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
except ImportError:
	try:
		import _GeoIP
		geoip = _GeoIP.new(_GeoIP.GEOIP_MEMORY_CACHE)
	except ImportError:
		geoip = None
		msg = _("""Soulserver supports a country code blocker but that requires a (GPL'ed) library called GeoIP. You can find it here:
	C library:       http://www.maxmind.com/app/c
	Python bindings: http://www.maxmind.com/app/python
	(the python bindings require the C library)""")
		log.addLine(msg)

	
def displayTraceback(exception=None):

	import traceback
	if exception is None:
		tb = traceback.format_tb(sys.exc_info()[2])
	else:
		tb = traceback.format_tb(exception)
	log.addError("Traceback: "+ str(sys.exc_info()[0].__name__) + ": "+str(sys.exc_info()[1]))
	for line in tb:
		if type(line) is tuple:
			xline = ""
			for item in line:
				xline += str(item) + " "
			line = xline

		line = line.strip("\n")
		log.addError(line)

	traceback.print_exc()
	
class SlskMessage:
	""" This is a parent class for all protocol messages. """
	
	def getObject(self, message, type, start=0, getintasshort=0, getsignedint=0):
		""" Returns object of specified type, extracted from message (which is
		a binary array). start is an offset."""
		intsize = struct.calcsize("<I")
		try:
			if type is types.IntType:
				if getintasshort:
					return intsize+start, struct.unpack("<H", message[start:start+struct.calcsize("<H")])[0]
				elif getsignedint:
					return intsize+start, struct.unpack("<i", message[start:start+intsize])[0]
				else:
					return intsize+start, struct.unpack("<I", message[start:start+intsize])[0]
			elif type is types.LongType:
				return struct.calcsize("<Q")+start, struct.unpack("<Q", message[start:start+struct.calcsize("<Q")])[0]
			elif type is types.StringType:
				length = struct.unpack("<I", message[start:start+intsize])[0]
				string = message[start+intsize:start+length+intsize]
				return length+intsize+start, string
			else:
				return start, None
		except struct.error, error:
			log.addError("%s %s" % (self.__class__, error))
			return start, None

	def packObject(self, object, signed=False):
		""" Returns object (integer, long or string packed into a 
		binary array."""
		if type(object) is types.IntType:
			return struct.pack("<i", object)
		elif type(object) is types.LongType:
			return struct.pack("<Q", object)
		elif type(object) is types.StringType:
			return struct.pack("<i", len(object))+object
		elif type(object) is types.UnicodeType:
			log.addError( _("Warning: networking thread has to convert unicode string %(object)s message %(type)s") % {'object':object, 'type':self.__class__} )
			encoded = object.encode("utf-8",'replace')
			return struct.pack("<i", len(encoded))+encoded
		log.addError( _("Warning: unknown object type %s") % type(object) +" "+ ("in message %(type)s") % {'type':self.__class__} +" "+  str(object) )
		return ""
		
	def packList(self, List):
		packed = ""
		# Pack List Number (needed for unpacking the list)
		packed += self.packObject(len(List))
		for item in List:
			packed += self.packObject(item)
		return packed
		
	def strrev(self, str):
		strlist = list(str)
		strlist.reverse()
		return ''.join(strlist)
	def strunreverse(self, string):
		strlist = string.split(".")
		strlist.reverse()
		return '.'.join(strlist)
	def debug(self, message=None):
		log.addLine(self.__dict__)
		if message:
			log.addLine( "Message contents: %s" % message.__repr__() )
			
class ServerMessage(SlskMessage):
	pass

class Login(ServerMessage):
	""" Message 1 """
	""" We sent this to the server right after the connection has been 
	established. Server responds with the greeting message. """
	
	def __init__(self, username=None, password=None, version = None, success=None, ip=None, checksum=None, reason=None):
		self.username = username
		self.password = password
		self.version = version
		self.ip = ip
		self.success = success
		self.checksum = checksum
		self.reason = reason
		
	def makeNetworkMessage(self):
		# Don't pack the ip
		message = chr(self.success) + self.packObject(self.reason) + socket.inet_aton(self.strunreverse(self.ip)) + self.packObject(self.checksum)
		return message
		

	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType, 0)
		pos, self.password = self.getObject(message, types.StringType, pos)
		try:
			pos, self.version = self.getObject(message, types.IntType, pos)
		except:
			log.addParsingError( "%s's version not found" %self.username)
		try:
			pos, self.checksum = self.getObject(message, types.StringType, pos)
		except:
			log.addParsingError( "%s's checksum not found" %self.username)
		try:
			pos, self.release = self.getObject(message, types.IntType, pos)
		except:
			log.addParsingError( "%s's release not found" %self.username)


class ChangePassword(ServerMessage):
	""" Message 142 """
	""" We sent this to the server to change our password 
	We receive a response if our password changes. """
	
	def __init__(self, password=None):
		self.password = password

	def makeNetworkMessage(self):
		return self.packObject(self.password)

	def parseNetworkMessage(self, message):
		pos, self.password = self.getObject(message, types.StringType)
		
class AdminMessage(ServerMessage):
	""" Message 66 """
	""" A global message from the admin has arrived """
	def __init__(self, msg):
		self.msg = msg
		
	def makeNetworkMessage(self):
		return self.packObject(self.msg)

class SetWaitPort(ServerMessage):
	""" Message 2 """
	""" Send this to server to indicate port number that we listen on."""
	def __init__(self, port=None):
		self.port = port
	
	def parseNetworkMessage(self, message):
		pos, self.port = self.getObject(message, types.IntType, 0)
		
class ServerPing(ServerMessage):
	""" Message 32 """
	def makeNetworkMessage(self):
		return ""
		
	def parseNetworkMessage(self, message):
		pass
	
class Relogged(ServerMessage):
	""" Message 41 """
	""" Server sends this if someone else logged in under our nickname
	and then disconnects us """
	def makeNetworkMessage(self):
		return ""
		
class SharedFoldersFiles(ServerMessage):
	""" Message 35 """
	""" We send this to server to indicate the number of folder and files
	that we share """
	def __init__(self, folders = None, files = None):
		self.folders = folders
		self.files = files
	
	def parseNetworkMessage(self, message):
		pos, self.folders = self.getObject(message, types.IntType, 0)
		pos, self.files = self.getObject(message, types.IntType, pos)
		
class SendSpeed(ServerMessage):
	""" Message 34 """
	""" Defunct message.. it used to be sent after a finished download to let the server update
	the speed statistics for a user, but was abused to display negative integers
	This should be Ignored
	Now using SendUploadSpeed
	"""
	def __init__(self, user = None, speed = None):
		self.user = user
		self.speed = speed
	def parseNetworkMessage(self, message):
		pos, self.user = self.getObject(message, types.StringType)
		pos, self.speed = self.getObject(message, types.IntType, pos)


class SendUploadSpeed(ServerMessage):
	""" Message 121 """
	""" We are sent this after a finished upload to let the server update
	the speed statistics for a user"""
	def __init__(self, speed = None):
		self.speed = speed
	
	def parseNetworkMessage(self, message):
		pos, self.speed = self.getObject(message, types.IntType)
		
		
class SetStatus(ServerMessage):
	""" Message 28 """
	""" We send our new status to the server """
	def __init__(self, status = None):
		self.status = status
	
	def makeNetworkMessage(self):
		return self.packObject(self.status)
		
	def parseNetworkMessage(self, message):
		pos, self.status = self.getObject(message, types.IntType, 0)
		
class GetUserStatus(ServerMessage):
	""" Message 7 """
	""" Server tells us if a user has gone away or has returned"""
	def __init__(self, username = None, status=None, privileged=None):
		self.username = username
		self.privileged = privileged
		self.status = status

	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(self.status) +chr(self.privileged)

	def parseNetworkMessage(self, message):
	
		pos, self.username = self.getObject(message, types.StringType)
	

class GetPeerAddress(ServerMessage):
	""" Message 3 """
	""" Used to find out a peer's (ip, port) address."""
	def __init__(self, username = None, ip = None, port = None):
		self.username = username
		self.ip = ip
		self.port = port 
		
	def makeNetworkMessage(self):
		return self.packObject(self.username) +  socket.inet_aton(self.strunreverse(self.ip)) +  self.packObject(self.port)
	
	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
		
class AddUser(ServerMessage):
	""" Message 5 """
	""" Used to be kept updated about a user's status."""
	def __init__(self, username = None, exists=None, status=None, speed=None, downloads=None, files=None, dirs=None, country=None):
		self.username = username
		self.exists = exists
		self.status = status
		self.speed = speed
		self.downloads = downloads
		self.files = files
		self.dirs = dirs
		self.country = country
		
	def makeNetworkMessage(self):
		if self.exists:
			return self.packObject(self.username) + chr(self.exists) + self.packObject(self.status) + self.packObject(self.speed) + self.packObject(long(self.downloads)) + self.packObject(self.files) + self.packObject(self.dirs) + self.packObject(self.country)
		else:
			return self.packObject(self.username) + chr(self.exists)

	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
		
class GetUserStats(ServerMessage):
	""" Server sends this to indicate change in user's statistics"""
	def __init__(self, username = None, speed=None, downloads=None, files=None, dirs=None):
		self.username = username
		self.speed = speed
		self.downloads = downloads
		self.files = files
		self.dirs = dirs
	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(self.speed) + self.packObject(long(self.downloads)) + self.packObject(self.files) + self.packObject(self.dirs)


"""
Chatrooms
"""
class SayChatroom(ServerMessage):
	""" Message 13 """
	""" Either we want to say something in the chatroom, or someone did."""
	def __init__(self, room = None, username = None, msg = None):
		self.room = room
		self.username = username
		self.msg = msg
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)+self.packObject(self.username)+self.packObject(self.msg)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.msg = self.getObject(message, types.StringType, pos)
	
class JoinRoom(ServerMessage):
	""" Message 14 """
	""" Server sends us this message when we join a room. Contains users list
	with data on everyone."""
	def __init__(self, room = None, private = None, users=None, stats=None, owner=None, operators=None):
		self.room = room
		self.private = private
		self.users = users
		self.stats = stats
		self.owner = owner
		self.operators = operators
	
	def makeNetworkMessage(self):
		if not self.private:
			return self.packObject(self.room) + self.getUsers()
		else:
			return self.packObject(self.room) + self.getUsers() + self.packObject(self.owner) + self.packList(self.operators)
			
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType, 0)
		if len(message[pos:]) == 1:
			pos, self.private = pos+1, bool(ord(message[pos:pos+1]))
		elif len(message[pos:]) >= 4:
			pos, self.private = self.getObject(message, types.IntType, pos)

	
	def getUsers(self):
		# Number of Users
		message = self.packObject(len(self.users))
		# List of Users
		for user in self.users:
			message += self.packObject(user)
		# Number of Users - Status
		message += self.packObject(len(self.users))
		# Users' Status
		for user in self.users:
			if user in self.stats:
				message += self.packObject(self.stats[user].status)
			else:
				message += self.packObject(0)
		# Number of Users - Speed, Downloads, Files, Dirs
		message += self.packObject(len(self.users))
		# Speed, Downloads, Files, Dirs
		for user in self.users:
			if user in self.stats:
				message += self.packObject(self.stats[user].speed)
				message += self.packObject(long(self.stats[user].downloads))
				message += self.packObject(int(self.stats[user].files))
				message += self.packObject(int(self.stats[user].dirs))
			else:
				message += self.packObject(0)
				message += self.packObject(long(0))
				message += self.packObject(0)
				message += self.packObject(0)
		# Number of users - slots
		message += self.packObject(len(self.users))
		# Slots
		for user in self.users:
			if user in self.stats:
				message += self.packObject(self.stats[user].slotsfull)
			else:
				message += self.packObject(0)
		# Number of users - country code
		message += self.packObject(len(self.users))
		# Country code
		for user in self.users:
			if user in self.stats:
				message += self.packObject(self.stats[user].country)
			else:
				message += self.packObject("")
		return message
		
class LeaveRoom(ServerMessage):
	""" Message 15 """
	""" We send this when we want to leave a room."""
	def __init__(self, room = None):
		self.room = room
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)


class UserJoinedRoom(ServerMessage):
	""" Message 16 """
	""" Server tells us someone has just joined the room."""
	def __init__(self, room = None, username = None, stats = None):
		self.room = room
		self.username = username
		self.stats = stats
		
	def makeNetworkMessage(self):
		return self.packObject(self.room) +  self.packObject(self.username) + self.packObject(int(self.stats.status)) + self.packObject(int(self.stats.speed)) + self.packObject(long(self.stats.downloads)) + self.packObject(int(self.stats.files)) + self.packObject(int(self.stats.dirs)) + self.packObject(self.stats.slotsfull) + self.packObject(self.stats.country)


class UserLeftRoom(ServerMessage):
	""" Message 17 """
	""" Notify a user that a different user left a room."""
	def __init__(self, room = None, username = None):
		self.room = room
		self.username = username
		
	def makeNetworkMessage(self):
		return self.packObject(self.room) +  self.packObject(self.username)
		
class RoomList(ServerMessage):
	""" Message 64 """
	""" Server tells us a list of rooms"""
	def __init__(self, rooms=None, ownedrooms=None, privaterooms=None, operatorrooms=None):
		self.rooms = rooms
		self.ownedrooms = ownedrooms
		self.privaterooms = privaterooms
		self.operatorrooms = operatorrooms
	
	def makeNetworkMessage(self):
		
		message = self.packObject(len(self.rooms))
		for room in self.rooms:
			message += self.packObject(room)
		message += self.packObject(len(self.rooms))
		for room in self.rooms:
			message += self.packObject(len(self.rooms[room].users))
			
		message += self.packObject(len(self.ownedrooms))
		for room in self.ownedrooms:
			message += self.packObject(room)
		message += self.packObject(len(self.ownedrooms))
		for room in self.ownedrooms:
			message += self.packObject(len(self.ownedrooms[room].users))
			
		message += self.packObject(len(self.privaterooms))
		for room in self.privaterooms:
			message += self.packObject(room)
		
		message += self.packObject(len(self.privaterooms))
		for room in self.privaterooms:
			message += self.packObject(len(self.privaterooms[room].users))
			
		message += self.packObject(len(self.operatorrooms))
		for room in self.operatorrooms:
			message += self.packObject(room)
		
		message += self.packObject(len(self.operatorrooms))
		for room in self.operatorrooms:
			message += self.packObject(len(self.operatorrooms[room].users))
	
			
		
		return message
		
	
	def parseNetworkMessage(self, message):
		pass

# Room tickers
class RoomTickerState(ServerMessage):
	""" Message 113 """
	def __init__(self, room=None, username=None, msgs={}):
		self.room = room
		self.username = username
		self.msgs = msgs
	
	def makeNetworkMessage(self):
		message = self.packObject(self.room)
		message += self.packObject(len(self.msgs))
		for user, msg in self.msgs.items():
			message += self.packObject(user)
			message += self.packObject(msg)
		return message


class RoomTickerAdd(ServerMessage):
	""" Message 114 """
	def __init__(self, room=None, username=None, msg=None):
		self.room = room
		self.username = username
		self.msg = msg
	
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username) + self.packObject(self.msg)

class RoomTickerRemove(ServerMessage):
	""" Message 115 """
	def __init__(self, room=None, username=None):
		self.username = username
		self.room = room
	
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username)

class RoomTickerSet(ServerMessage):
	""" Message 116 """
	def __init__(self):
		self.room = None
		self.msg = None
		
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.msg = self.getObject(message, types.StringType, pos)
		
""" PRIVATE ROOMS """

class PrivateRoomUsers(ServerMessage):
	""" Message 133 """
	""" We get this when we've created a private room."""
	def __init__(self, room = None, numusers = None, users = None):
		self.room = room
		self.numusers = numusers
		self.users = users
		
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packList(self.users)


class PrivateRoomAddUser(ServerMessage):
	""" Message 134 """
	""" We get / receive this when we add a user to a private room."""
	def __init__(self, room = None, username = None):
		self.room = room
		self.username = username
	
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.username = self.getObject(message, types.StringType, pos)
	

class PrivateRoomRemoveUser(ServerMessage):
	""" Message 135 """
	""" We get this when we've removed a user from a private room."""
	def __init__(self, room = None, username = None):
		self.room = room
		self.username = username
	
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.username = self.getObject(message, types.StringType, pos)
		
class PrivateRoomDismember(ServerMessage):
	""" Message 136 """
	""" We do this to remove our own membership of a private room."""
	def __init__(self, room = None):
		self.room = room

	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		
class PrivateRoomDisown(ServerMessage):
	""" Message 137 """
	""" We do this to stop owning a private room."""
	def __init__(self, room = None):
		self.room = room

	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		
class PrivateRoomSomething(ServerMessage):
	""" Message 138 """
	"""Unknown"""
	def __init__(self, room = None):
		self.room = room

	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		
class PrivateRoomAdded(ServerMessage):
	""" Message 139 """
	""" We are sent this when we are added to a private room."""
	def __init__(self, room = None):
		self.room = room
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		self.room = self.getObject(message, types.StringType)[1]
		
class PrivateRoomRemoved(ServerMessage):
	""" Message 140 """
	""" We are sent this when we are removed from a private room."""
	def __init__(self, room = None):
		self.room = room
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		self.room = self.getObject(message, types.StringType)[1]
		
class PrivateRoomToggle(ServerMessage):
	""" Message 141 """
	""" We send this when we want to enable or disable invitations to private rooms"""
	def __init__(self, enabled = None):
		self.enabled = enabled
	
	def makeNetworkMessage(self):
		return chr(self.enabled)
	
	def parseNetworkMessage(self, message):
		# When this is received, we store it in the config, and disable the appropriate menu item
		pos, self.enabled = 1, bool(ord(message[0]))
		
class PrivateRoomAddOperator(ServerMessage):
	""" Message 143 """
	""" We send this to add private room operator abilities to a user"""
	def __init__(self, room = None, username = None):
		self.room = room
		self.username = username
	
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.username = self.getObject(message, types.StringType, pos)
		
class PrivateRoomRemoveOperator(ServerMessage):
	""" Message 144 """
	""" We send this to remove privateroom operator abilities from a user"""
	def __init__(self, room = None, username = None):
		self.room = room
		self.username = username
	
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.username = self.getObject(message, types.StringType, pos)
		
class PrivateRoomOperatorAdded(ServerMessage):
	""" Message 145 """
	""" We receive this when given privateroom operator abilities"""
	def __init__(self, room = None):
		self.room = room
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		
class PrivateRoomOperatorRemoved(ServerMessage):
	""" Message 146 """
	""" We receive this when privateroom operator abilities are removed"""
	def __init__(self, room = None):
		self.room = room
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)

	
class PrivateRoomOwned(ServerMessage):
	""" Message 148 """
	""" We get this when we've created a private room."""
	def __init__(self, room = None, number = None, operators = None):
		self.room = room
		self.number = number
		self.operators = operators
		
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packList(self.operators)
		

""" PUBLIC ROOM """
class JoinPublicRoom(ServerMessage):
	""" Message 150 """
	"""We want to join the Public Chat"""
	def __init__(self):
		pass
	def parseNetworkMessage(self, message):
		pass
		#pos, self.code = self.getObject(message, types.StringType)
		
class LeavePublicRoom(ServerMessage):
	""" Message 151 """
	"""We want to leave the Public Chat"""
	def __init__(self):
		pass
	def parseNetworkMessage(self, message):
		pass
		#pos, self.code = self.getObject(message, types.StringType)

class PublicRoomMessage(ServerMessage):
	""" Message 152 """
	"""The server sends messages from random chatrooms"""
	def __init__(self, room=None, username=None, msg=None):
		self.username = username
		self.room = room
		self.msg = msg
	def makeNetworkMessage(self):
		return self.packObject(self.room) + self.packObject(self.username)  + self.packObject(self.msg)

"""
Private Chat messages
"""
class MessageUser(ServerMessage):
	""" Message 22 """
	""" Chat phrase sent to someone or received by us in private"""
	def __init__(self, msgid=None, timestamp=None, username = None, msg = None):
		self.msgid = msgid
		self.timestamp = timestamp
		self.username = username
		self.msg = msg
		
	
	def makeNetworkMessage(self):
		return self.packObject(self.msgid)+self.packObject(self.timestamp) +self.packObject(self.username)+self.packObject(self.msg)
	
	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
		pos, self.msg = self.getObject(message, types.StringType, pos)
		
class MessageAcked(ServerMessage):
	""" Message 23 """
	""" Confirmation of private chat message.
	If we don't send it, the server will keep sending the chat phrase to us.
	"""
	def __init__(self, msgid = None):
		self.msgid = msgid
	
	def parseNetworkMessage(self, message):
		pos, self.msgid = self.getObject(message, types.IntType)

"""
Privileges
"""

class PrivilegedUsers(ServerMessage):
	""" Message 69 """
	""" A list of those who made a donation """
	def __init__(self, users=None):
		self.users = users
		if self.users is None:
			self.users = []
	def makeNetworkMessage(self):
		message = self.packList(self.users)
		try:
			message = zlib.compress(self.packObject(1)+message)
		except Exception, error:
			pass
		else:
			return message
			
class AddToPrivileged(ServerMessage):
	""" Message 91 """
	def __init__(self, username=None):
		self.username = username
	
	def makeNetworkMessage(self):
		return self.packObject(self.username) 
		
class CheckPrivileges(ServerMessage):
	""" Message 92 """
	def __init__(self, seconds=0):
		self.seconds = seconds
	
	def makeNetworkMessage(self):
		return self.packObject(self.seconds) 
	
	def parseNetworkMessage(self, message):
		pass

class UserPrivileged(ServerMessage):
	""" Message 122 """
	""" Discover whether a user is privileged or not """
	def __init__(self, username = None, privileged = None):
		self.username = username
		self.privileged = None

	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(bool(self.privileged))

	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType, pos)

class GivePrivileges(ServerMessage):
	""" Message 123 """
	""" Give (part) of your privileges to another user on the network """

	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
		pos, self.days = self.getObject(message, types.IntType, pos)
	
class NotifyPrivileges(ServerMessage):
	""" Message 124 """
	""" Server tells us something about privileges"""
	def __init__(self, token = None, user = None):
		self.token = token
		self.username = user
		
	def parseNetworkMessage(self, message):
		pos, self.token = self.getObject(message, types.IntType)
		pos, self.username = self.getObject(message, types.StringType, pos)
	def makeNetworkMessage(self):
		return self.packObject(self.token) + self.packObject(self.username)
	
class AckNotifyPrivileges(ServerMessage):
	""" Message 125 """
	def __init__(self, token = None):
		self.token = token
	def parseNetworkMessage(self, message):
		pos, self.token = self.getObject(message, types.IntType)
	def makeNetworkMessage(self):
		return self.packObject(self.token)

"""
INTERESTS - Likes, Dislikes and Recommendations
"""
class AddThingILike(ServerMessage):
	""" Message 51 """
	""" Add item to our likes list """
	def __init__(self, thing = None):
		self.thing = thing
	
	def parseNetworkMessage(self, message):
		pos, self.thing = self.getObject(message, types.StringType)

class AddThingIHate(AddThingILike):
	""" Message 117 """
	pass

class RemoveThingILike(AddThingILike):
	""" Message 52 """
	""" Remove item from our likes list """
	pass

class RemoveThingIHate(AddThingILike):
	""" Message 118 """
	pass

class UserInterests(ServerMessage):
	""" Message 57 """
	def __init__(self, username = None, likes=None, hates=None):
		self.username = username
		self.likes = likes
		self.hates = hates
		
	def makeNetworkMessage(self):
		# Request a users' interests
		message = self.packObject(self.username)
		message += self.packObject(len(self.likes))
		for like in self.likes:
			message += self.packObject(like)
		message += self.packObject(len(self.hates))
		for hate in self.hates:
			message += self.packObject(hate)
		return message
	
	def parseNetworkMessage(self, message, pos=0):
		# Receive a users' interests
		pos, self.username = self.getObject(message, types.StringType, pos)

class GlobalRecommendations(ServerMessage):
	""" Message 56 """
	def __init__(self, recommendations=None, unrecommendations=None):
		self.recommendations = recommendations
		self.unrecommendations = unrecommendations
	
	def makeNetworkMessage(self):
		return self.pack_recommendations()
	
	def parseNetworkMessage(self, message):
		pass
	
	def pack_recommendations(self):
		message = self.packObject(len(self.recommendations))
		for like, rating in self.recommendations.items():
			message += self.packObject(like)
			message += self.packObject(rating)
		message += self.packObject(len(self.unrecommendations))
		for hate, rating in self.unrecommendations.items():
			message += self.packObject(hate)
			message += self.packObject(rating)
		return message
	
			
class Recommendations(GlobalRecommendations):
	""" Message 54 """
	pass
    
class ItemRecommendations(GlobalRecommendations):
	""" Message 111 """
	def __init__(self, thing = None, recommendations=None, unrecommendations=None):
		self.thing = thing
		self.recommendations = recommendations
		self.unrecommendations = unrecommendations
		GlobalRecommendations.__init__(self, self.recommendations, self.unrecommendations)
		
	
	def makeNetworkMessage(self):
		return self.packObject(self.thing) + self.pack_recommendations()
	
	def parseNetworkMessage(self, message):
		pos, self.thing = self.getObject(message, types.StringType)


class SimilarUsers(ServerMessage):
	""" Message 110 """
	def __init__(self, users=None):
		self.users = users
	
	def makeNetworkMessage(self):
		message = self.packObject(len(self.users))
		for user, rating in self.users.items():
			message += self.packObject(user)
			message += self.packObject(rating)
		return message
	
	def parseNetworkMessage(self, message):
		pass

		
class ItemSimilarUsers(ServerMessage):
	""" Message 112 """
	def __init__(self, thing = None, users=None):
		self.thing = thing
		self.users = None
	
	def makeNetworkMessage(self):
		message = self.packObject(self.thing)
		message += self.packObject(len(self.users))
		for user in self.users:
			message += self.packObject(user)
		return message
	
	def parseNetworkMessage(self, message):
		pos, self.thing = self.getObject(message, types.StringType)

# Searches
class FileSearch(ServerMessage):
	""" Message 26 """
	""" We send this to the server when we search for something."""
	""" Server send this to tell us someone is searching for something."""
	def __init__(self, username = None, searchid = None, searchterm = None):
		self.username = username
		self.searchid = searchid
		self.searchterm = searchterm
		#if text:
			#self.searchterm = ' '.join([x for x in text.split() if x != '-'])
	
	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(self.searchid) + self.packObject(self.searchterm)
	
	def parseNetworkMessage(self, message):
		pos, self.searchid = self.getObject(message, types.IntType)
		pos, self.searchterm = self.getObject(message, types.StringType, pos)
		
class WishListSearch(FileSearch):
	""" Message 103 """
	pass


class WishlistInterval(ServerMessage):
	""" Message 104 """
	def __init__(self, seconds=None):
		self.seconds = seconds
		
	def makeNetworkMessage(self):
		return self.packObject(self.seconds)


class ExactFileSearch(ServerMessage):
	""" Message 65 """
	""" Someone is searching for a file with an exact name """
	def __init__(self, username = None, request = None, filename = None, folder = None, size = None, checksum = None):
		self.username = username
		self.request = request
		self.filename = filename
		self.folder = folder
		self.size = size
		self.checksum = checksum
		
	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(self.request)  + self.packObject(self.filename)  + self.packObject(self.folder)  + self.packObject(self.size)  + self.packObject(self.checksum) 
		
	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
		pos, self.req = self.getObject(message, types.IntType, pos)
		pos, self.file = self.getObject(message, types.StringType, pos)
		pos, self.folder = self.getObject(message, types.StringType, pos)
		pos, size1 = self.getObject(message, types.IntType, pos)
		pos, size2 = self.getObject(message, types.IntType, pos)
		self.size = size1 + size2
		pos, self.checksum = self.getObject(message, types.IntType, pos)
		
class Md5Search(ServerMessage):
	# Todo
	""" Someone is searching for a file with an exact name """
	def __init__(self, username = None, checksum = None):
		self.username = username
		self.checksum = checksum
		
	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(self.checksum)
		
	#def parseNetworkMessage(self, message):
		#pos, self.username = self.getObject(message, types.StringType)
		#pos, self.checksum = self.getObject(message, types.IntType, pos)
		
class UserSearch(ServerMessage):
	""" Message 42 """
	def __init__(self, username=None, requestid = None, text = None):
		self.username = username
		self.searchid = requestid
		self.searchterm = text
	
	def makeNetworkMessage(self):
		return self.packObject(self.username)+self.packObject(self.searchid)+self.packObject(self.searchterm)
	
	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)
		pos, self.searchid = self.getObject(message, types.IntType, pos)
		pos, self.searchterm = self.getObject(message, types.StringType, pos)

class RoomSearch(ServerMessage):
	""" Message 120 """
	def __init__(self, room=None, requestid = None, text = None):
		self.room = room
		self.searchid = requestid
		self.searchterm = text
		if text:
			self.searchterm = ' '.join([x for x in text.split() if x != '-'])
	
	def makeNetworkMessage(self):
		return self.packObject(self.room)+self.packObject(self.searchid)+self.packObject(self.searchterm)
	
	def parseNetworkMessage(self, message):
		pos, self.room = self.getObject(message, types.StringType)
		pos, self.searchid = self.getObject(message, types.IntType, pos)
		pos, self.searchterm = self.getObject(message, types.StringType, pos)


class HaveNoParent(ServerMessage):
	""" Message 71 """
	def parseNetworkMessage(self, message):
		pos, self.noparent = 1, ord(message[0:1])
		
class SearchParent(ServerMessage):
	""" Message 73 """
	def __init__(self, parentip = None):
		self.parentip = parentip
	
	def parseNetworkMessage(self, message):
		pos = 0
		pos, self.parentip = pos+4, socket.inet_ntoa(self.strrev(message[pos:pos+4]))

		
class ParentMinSpeed(ServerMessage):
	""" Message 83 """
	def __init__(self, num = None):
		self.num = num
		
	def makeNetworkMessage(self):
		return self.packObject(self.num)

class ParentSpeedRatio(ParentMinSpeed):
	""" Message 84 """
	def __init__(self, num = None):
		self.num = num
		
	def makeNetworkMessage(self):
		return self.packObject(self.num)

class Msg85(ServerMessage):
	""" Message 85 """
	def __init__(self):
		pass
		
	def parseNetworkMessage(self, message):
		pass
	def makeNetworkMessage(self):
		return
		
		
class ParentInactivityTimeout(ServerMessage):
	""" Message 86 """
	def __init__(self, seconds = None):
		self.seconds = seconds

	def makeNetworkMessage(self):
		return self.packObject(self.seconds)

class SearchInactivityTimeout(ServerMessage):
	""" Message 87 """
	def __init__(self, seconds = None):
		self.seconds = seconds

	def makeNetworkMessage(self):
		return self.packObject(self.seconds)
		
class MinParentsInCache(ServerMessage):
	""" Message 88 """
	def __init__(self, num = None):
		self.num = num
	
	def makeNetworkMessage(self):
		return self.packObject(self.num)

class Msg89(ServerMessage):
	""" Message 89 """
	def __init__(self):
		pass
		
	def parseNetworkMessage(self, message):
		pass
	def makeNetworkMessage(self):
		return
		
class DistribAliveInterval(ServerMessage):
	""" Message 90 """
	def __init__(self, seconds = None):
		self.seconds = seconds

	def makeNetworkMessage(self):
		return self.packObject(self.seconds)
		


class AcceptChildren(ServerMessage):
	""" Message 100 """
	def __init__(self, enabled = None):
		self.enabled = enabled

	def makeNetworkMessage(self):
		return chr(self.enabled)
		
class BranchLevel(ServerMessage):
	""" Message 126 """
	def __init__(self, level=None):
		self.level = level

	def makeNetworkMessage(self):
		return self.packObject(self.level)
		
	def parseNetworkMessage(self, message):
		pos, self.level = self.getObject(message, types.IntType)

class BranchRoot(ServerMessage):
	""" Message 127 """
	def __init__(self, username=None):
		self.username = username

	def makeNetworkMessage(self):
		return self.packObject(self.username)
	def parseNetworkMessage(self, message):
		pos, self.username = self.getObject(message, types.StringType)

class ChildDepth(ServerMessage):
	""" Message 129 """
	def __init__(self):
		pass
	def makeNetworkMessage(self):
		return self.packObject(self.level)
	def parseNetworkMessage(self, message):
		pos, self.value = self.getObject(message, types.IntType)


class ConnectToPeer(ServerMessage):
	""" Either we ask server to tell someone else we want to establish a 
	connection with him or server tells us someone wants to connect with us.
	Used when the side that wants a connection can't establish it, and tries
	to go the other way around.
	"""
	def __init__(self, token=None, username=None, type=None, ip=None, port=None, unknown=None):
		self.token = token
		self.username = username
		self.type = type
		self.ip = ip
		self.port = port
		self.unknown = unknown
		
	def parseNetworkMessage(self, message):
		pos, self.token = self.getObject(message, types.IntType)
		pos, self.username = self.getObject(message, types.StringType)
		pos, self.type = self.getObject(message, types.StringType, pos)
		# Type is F, D 
	def makeNetworkMessage(self):
		return self.packObject(self.username) + self.packObject(self.type) + self.packObject(self.ip) +self.packObject(self.port) + self.packObject(self.token) + chr(self.unknown)
	
	#def parseNetworkMessage(self, message):
		#pos, self.username = self.getObject(message, types.StringType)
		#pos, self.type = self.getObject(message, types.StringType, pos)
		#pos, self.ip = pos+4, socket.inet_ntoa(self.strrev(message[pos:pos+4]))
		#pos, self.port = self.getObject(message, types.IntType, pos, 1)
		#pos, self.token = self.getObject(message, types.IntType, pos)
		#if len(message[pos:]) > 0:
			## Don't know what this is, may be some kind of status
			#pos, self.unknown = pos+1, ord(message[pos])
			
class CantConnectToPeer(ServerMessage):
	""" Message 1001 """
	""" We send this to say we can't connect to peer after it has asked us
	to connect. We receive this if we asked peer to connect and it can't do
	this. So this message means a connection can't be established either way.
	"""
	def __init__(self, token = None, username = None):
		self.token = token
		self.username = username
	
	def makeNetworkMessage(self):
		return self.packObject(self.token)
	
	def parseNetworkMessage(self, message):
		pos, self.token = self.getObject(message, types.IntType)
		pos, self.username = self.getObject(message, types.StringType, pos)
		
class Messages:
	servercodes = {
		Login:1,
		SetWaitPort:2,
		GetPeerAddress:3,
		AddUser:5,
		#Unknown6:6,
		GetUserStatus:7,
		SayChatroom:13,
		JoinRoom:14,
		LeaveRoom:15,
		UserJoinedRoom:16,
		UserLeftRoom:17,
		ConnectToPeer:18,
		MessageUser:22,
		MessageAcked:23,
		FileSearch:26,
		SetStatus:28,
		ServerPing:32,
		SendSpeed:34,
		SharedFoldersFiles:35,
		GetUserStats:36,
		#QueuedDownloads:40, 
		Relogged:41,
		UserSearch:42,
		AddThingILike:51,
		RemoveThingILike:52,
		Recommendations:54,
		GlobalRecommendations:56,
		UserInterests:57,
		#PlaceInLineResponse:60, # Depreciated?
		#RoomAdded:62, # Depreciated
		#RoomRemoved:63, # Depreciated
		RoomList:64,
		ExactFileSearch:65,
		AdminMessage:66,
		#GlobalUserList:67, # Depreciated?
		#TunneledMessage:68,  # Depreciated?
		PrivilegedUsers:69,
		HaveNoParent:71,
		SearchParent:73,
		ParentMinSpeed:83,
		ParentSpeedRatio:84,
		Msg85:85, # Unknown message
		ParentInactivityTimeout:86,
		SearchInactivityTimeout:87,
		MinParentsInCache:88,
		Msg89:89, # Unknown message
		DistribAliveInterval:90,
		AddToPrivileged:91,
		CheckPrivileges:92,
		#SearchRequest:93,
		#AcceptChildren:100,
		#NetInfo:102,
		WishListSearch:103,
		WishlistInterval:104,
		SimilarUsers:110,
		ItemRecommendations:111,
		ItemSimilarUsers:112,
		RoomTickerState:113,
		RoomTickerAdd:114,
		RoomTickerRemove:115,
		RoomTickerSet:116,
		AddThingIHate:117,
		RemoveThingIHate:118,
		RoomSearch:120, 
		SendUploadSpeed:121,
		UserPrivileged:122,
		GivePrivileges:123,
		NotifyPrivileges:124,
		AckNotifyPrivileges:125,
		BranchLevel:126,
		BranchRoot:127,
		ChildDepth:129,
		##AnotherStatus:10,
		PrivateRoomUsers:133,
		PrivateRoomAddUser:134,
		PrivateRoomRemoveUser:135,
		PrivateRoomDismember:136,
		PrivateRoomDisown:137,
		PrivateRoomSomething:138,
		PrivateRoomAdded:139,
		PrivateRoomRemoved:140,
		PrivateRoomToggle:141,
		ChangePassword:142,
		PrivateRoomAddOperator:143,
		PrivateRoomRemoveOperator:144,
		PrivateRoomOperatorAdded:145,
		PrivateRoomOperatorRemoved:146,
		PrivateRoomOwned:148,
		JoinPublicRoom:150,
		LeavePublicRoom:151,
		PublicRoomMessage:152,
		CantConnectToPeer:1001,
		}
	def __init__(self):
		self.serverclasses = {}
		for i in self.servercodes.keys():
			self.serverclasses[self.servercodes[i]] = i
	


class RoomData:
	""" When we join a room the server send us a bunch of these, 
	for each user."""
	def __init__(self, name=None, users=None, private=False, owner=None, operators=None, tickers=None, hidden=False):
		self.name = name
		self.users = users
		if self.users is None:
			self.users = []
		self.private = private
		self.owner = owner
		self.operators = operators
		if self.operators is None:
			self.operators = []
		self.tickers = tickers
		if self.tickers is None:
			self.tickers = {}
		self.hidden = hidden
class UserData:
	""" When we join a room the server send us a bunch of these, 
	for each user."""
	def __init__(self, username=None, password=None, client_version=None, ip=None, port=None, logged_in=False, admin=False, privileges=0, status=0, speed=0, downloads=0, files=0, dirs=0, slotsfull=0, country="", likes=None, hates=None, last_connected_time=None, last_message_time=None):
		self.username = username
		self.password = password
		self.client_version = client_version
		self.ip = ip
		self.port = port
		self.logged_in = logged_in
		self.admin = admin
		self.privileges = privileges
		self.status = status
		self.speed = speed
		self.downloads = downloads # Long
		self.files = files # Int
		self.dirs = dirs # Int
		self.slotsfull = slotsfull # Int
		self.country = country # Two-Character String ex: "US", "CA", or "RU"
		self.likes = likes
		self.hates = hates
		if self.likes is None:
			self.likes = []
		if self.hates is None:
			self.hates = []
		self.last_connected_time = last_connected_time
		self.last_message_time = last_message_time
		# Other users that are watching this user
		self.IsWatchedBy=[]
		# can join/be invited to private rooms
		self.privaterooms=False
		
	def addLike(self, like):
		if like not in self.likes:
			self.likes.append(like)
	def removeLike(self, like):
		if like in self.likes:
			self.likes.remove(like)
	def addHate(self, hate):
		if hate not in self.hates:
			self.hates.append(hate)
	def removeHate(self, hate):
		if hate in self.hates:
			self.hates.remove(hate)
	def setPrivileges(self, privileges):
		self.privileges = privileges
	def setAdmin(self, isadmin):
		self.admin = isadmin
	def setUsername(self, username):
		self.username = username
	def setPassword(self, password):
		self.password = password
	def setIP(self, ip):
		self.ip = ip
	def setPort(self, port):
		self.port = port
	def setLoggedIn(self, logged_in):
		self.logged_in = logged_in
	def setStatus(self, status):
		self.status = status
	def setSpeed(self, avgspeed):
		self.avgspeed = avgspeed
	def setDownloads(self, downloadnum):
		self.downloadnum = downloadnum
	def setFiles(self, files):
		self.files = files
	def setDirectories(self, dirs):
		self.dirs = dirs
	def setSlots(self, slotsfull):
		self.slotsfull = slotsfull
	def setCountry(self, country):
		self.country = country
		
class Privileges:
	def __init__(self, server):
		self.server = server
		
	def PrivilegedUsers(self, client):
		privileged =[]
		for user in self.server.users:
			data = self.server.users[user]
			if data.privileges > 0:
				privileged.append(user)
		newmsg = PrivilegedUsers(privileged)
		self.server.Send(client, newmsg)
		
	def PrivilegesUserAdd(self):
		pass
	
	def PrivilegesUserRemove(self):
		pass
	
	def GivePrivileges(self, giveuser, days, receiveuser):
		if giveuser not in self.server.users:
			return
		giveprivs = self.server.users[giveuser].privileges
		seconds = days * 3600 * 24
		if seconds > giveprivs:
			lookup = self.server.FindClientFromUsername(giveuser)
			newmsg = AdminMessage("You do not have enough privileges to give %s days to %s" % (days, giveuser))
			self.server.Send(lookup, newmsg)
			return
		if receiveuser in self.server.users:
			self.server.users[receiveuser].privileges += seconds
			if giveuser not in self.server.admins:
				self.server.users[giveuser].privileges -= seconds
			newmsg = AdminMessage("%s has given you %s day(s) of privileges" % (giveuser, days))
			lookup = self.server.FindClientFromUsername(receiveuser)
			self.server.Send(lookup, newmsg)
			
class Searches:
	def __init__(self, server):
		self.server = server
		self.parentminspeed = 1
		self.parentspeedratio = 50
		
	def WishListSearch(self, username,  searchid, searchterm):
		# Todo: needs a timer, and proper Implementation
		newmsg = FileSearch(username=username, searchid=searchid, searchterm=searchterm)
		for user, data in self.server.users.items():
			if not data.logged_in:
				continue
			lookup = self.server.FindClientFromUsername(user)
			self.server.Send(lookup, newmsg)
	
	def FileSearch(self, username, searchid, searchterm):
		newmsg = FileSearch(username=username, searchid=searchid, searchterm=searchterm)
		for user, data in self.server.users.items():
			if not data.logged_in:
				continue
			lookup = self.server.FindClientFromUsername(user)
			self.server.Send(lookup, newmsg)
			
	def ExactFileSearch(self, username, searcheduser, request, filename, folder, size, checksum):
		
		if searcheduser not in self.server.users :
			return
		if not self.server.users[searcheduser].logged_in:
			return
		newmsg = ExactFileSearch(username=username, request=request, filename=filename, folder=folder, size=size, checksum=checksum)
		lookup = self.server.FindClientFromUsername(searcheduser)
		self.server.Send(lookup, newmsg)
	
	def MD5Search(self, md5sum):
		# Todo: new message, with only checksum
		pass
	
	def UserSearch(self, username, searcheduser, searchid, searchterm):
		if searcheduser not in self.server.users :
			return
		if not self.server.users[searcheduser].logged_in:
			return
		lookup = self.server.FindClientFromUsername(searcheduser)
		if lookup is not None:
			newmsg = FileSearch(username=username, searchid=searchid, searchterm=searchterm)
			self.server.Send(lookup, newmsg)
			
	def RoomSearch(self, username, room, searchid, searchterm):
		if room in self.server.rooms.keys():
			newmsg = FileSearch(username=username, searchid=searchid, searchterm=searchterm)
			for user in self.server.rooms[room].users:
				if user != username:
					lookup = self.server.FindClientFromUsername(user)
					self.server.Send(lookup, newmsg)
	
class SoulServer(object):
	""" Simple chat server using select """

	def __init__(self, port=2242, backlog=5):
		self.clients = 0
		# Client map
		self.Messages = Messages()
		self.Searches = Searches(self)
		self.clientmap = {}
		
		self.users = self.readFile("userlist.db")
		self.privatemessages = self.readFile("privatemessages.db")
		
		self.rooms = self.readFile("rooms.db")
		log.addLine("%s users read" % len(self.users))
		log.addLine("%s users have private messages waiting" % len(self.privatemessages))
		log.addLine("%s rooms saved" % len(self.rooms))
		self.admins = {}
		# Output socket list
		# Reset all users to Offline (since the server just started, and no one is online yet)
		for user, data in self.users.items():
			data.logged_in=False
			data.status=0
			if "privileges" not in data.__dict__:
				data.privileges=0
			#if "IsWatchedBy" not in data.__dict__:
			data.IsWatchedBy=[]
			data.privaterooms=False
		for room in self.rooms.keys()[:]:
			data = self.rooms[room]
			if not data.private and not data.hidden:
				del self.rooms[room]
				continue
			data.users = []
			data.tickers = {}
			
		self.Privileges = Privileges(self)
		self.outputs = []



		self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		try:
			self.server.bind(('',port))
		except Exception, e:
			log.addError("Another program is using port %s, cannot start soulserver: %s" % (port, e))
			sys.exit(1)
		log.addLine( 'Listening to port %s ...' % port)
		self.server.listen(backlog)
		# Trap keyboard interrupts
		signal.signal(signal.SIGINT, self.sighandler)
		self.events = {Login:self.Login, 
		str:self.Log, 
		ChangePassword:self.ChangePassword,
		SetWaitPort:self.SetWaitPort,
		RoomList:self.RoomListRequested,
		JoinRoom: self.JoinRoom,
		LeaveRoom:self.LeaveRoom,
		AddUser:self.AddUser,
		GetUserStatus:self.GetUserStatus,
		#UserLeftRoom:self.UserLeftRoom,
		ServerPing:self.ServerPing,
		SharedFoldersFiles:self.SharedFoldersFiles,
		SetStatus: self.SetStatus,
		GetPeerAddress:self.GetPeerAddress,
		GetUserStats:self.GetUserStats,
		SayChatroom:self.SayChatroom,
		MessageUser:self.MessageUser,
		MessageAcked:self.MessageAcked,
		RemoveThingIHate:self.RemoveThingIHate,
		AddThingIHate:self.AddThingIHate,
		RemoveThingILike:self.RemoveThingILike,
		AddThingILike:self.AddThingILike,
		NotifyPrivileges:self.NotifyPrivileges,
		AckNotifyPrivileges:self.AckNotifyPrivileges,
		GivePrivileges:self.GivePrivileges,
		UserInterests:self.UserInterests,
		GlobalRecommendations:self.GlobalRecommendations,
		Recommendations:self.Recommendations,
		ItemRecommendations:self.ItemRecommendations ,
		SimilarUsers:self.SimilarUsers,
		ItemSimilarUsers:self.ItemSimilarUsers,
		RoomTickerState:self.RoomTickerState ,
		RoomTickerSet:self.RoomTickerSet,
		FileSearch:self.FileSearch,
		WishListSearch:self.WishListSearch,
		RoomSearch:self.RoomSearch,
		UserSearch:self.UserSearch,
		ExactFileSearch:self.ExactFileSearch,
		Md5Search:self.Md5Search,
		CheckPrivileges:self.CheckPrivileges,
		PrivateRoomToggle:self.PrivateRoomToggle,
		}
	
		
	def writeData(self):
		self.writeFile("userlist.db", self.users)
		self.writeFile("privatemessages.db", self.privatemessages)
		self.writeFile("rooms.db", self.rooms)
		log.addLine("%s users written" % len(self.users))
		pmcount = 0
		for user, messages in self.privatemessages.items():
			pmcount += len(messages)
		log.addLine("%s private messages written" % pmcount)
		log.addLine("%s rooms written" % len(self.rooms))
		
	def writeFile(self, datafile, data):
		f = open(datafile,"w")
		cPickle.dump(data, f, 1)
		f.close()
		
	def readFile(self, datafile):
		try:
			if os.path.exists(datafile):
				f = open(datafile)
				data = cPickle.load(f)
				f.close()
			else:
				data = {}
		except Exception, e:
			log.addError( e )
			data = {}
		return data
		
	def Log(self, msg):
		log.addLine(msg)
	
	def FindMsgOwner(self, msg):
		if msg is None:
			return
		for client in self.clientmap:
			if msg in self.clientmap[client]["messages"]:
				return client
		return
		
	def ClientUsername(self, client):
		if client is None:
			return
		if "username" in self.clientmap[client]:
			return self.clientmap[client]["username"]
		return 
		
	def FindClientFromUsername(self, username):
		if username is None:
			return
		for client in self.clientmap:
			if username == self.clientmap[client]["username"]:
				return client
		return
		
	def ClientAndUsername(self, msg):
		if msg is None:
			return
		newclient = None
		username = None
		for client in self.clientmap:
			if msg in self.clientmap[client]["messages"]:
				username = self.clientmap[client]["username"]
				newclient = client
				break
		return (newclient, username)
		
	def AckNotifyPrivileges(self, msg):
		# Todo: 
		msg.debug()
	
	def NotifyPrivileges(self, msg):
		# Todo: 
		(client, username) = self.ClientAndUsername(msg)
		if client is None or username is None:
			return
		if username != msg.username:
			return
		newmsg = AckNotifyPrivileges(msg.token)
		self.Send(client, newmsg)
		msg.debug()

		
	def UserSearch(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if username is None:
			return
		self.Searches.UserSearch(username, msg.username, msg.searchid, msg.searchterm)
		
	def RoomSearch(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if username is None:
			return
		self.Searches.RoomSearch(username, msg.room, msg.searchid, msg.searchterm)
		
	def FileSearch(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if username is None:
			return
		self.Searches.FileSearch(username, msg.searchid, msg.searchterm)
		
	def ExactFileSearch(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if username is None:
			return
		self.Searches.ExactFileSearch(username, msg.username, msg.request, msg.filename, msg.folder, msg.size, msg.checksum)
		
	def WishListSearch(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if username is None:
			return
		self.Searches.WishListSearch(username, msg.searchid, msg.searchterm)
		
	def Md5Search(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if username is None:
			return
		self.Searches.Md5Search(username, msg.checksum)
		
	def AddThingIHate(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		log.addLine ("%s hates %s" % (username, msg.thing) )
		if username in self.users:
			self.users[username].addHate(msg.thing)
			
	def AddThingILike(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		log.addLine ("%s likes %s" % (username, msg.thing) )
		if username in self.users:
			hates = self.users[username].addLike(msg.thing)
			
	def RemoveThingILike(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		log.addLine ("%s doesn't like %s" % (username, msg.thing) )
		if username in self.users:
			hates = self.users[username].removeLike(msg.thing)
				
	def RemoveThingIHate(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		log.addLine ("%s doesn't hate %s" % (username, msg.thing) )
		if username in self.users:
			hates = self.users[username].removeHate(msg.thing)
			
	def AddUser(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if msg.username  in self.users:
			data = self.users[msg.username]
			if username not in data.IsWatchedBy:
				data.IsWatchedBy.append(username)
			newmsg = AddUser(username = data.username, exists=1, status=data.status, speed=data.speed, downloads=data.downloads, files=data.files, dirs=data.dirs, country=data.country)
		else:
			newmsg = AddUser(username = msg.username, exists=0, status=0, speed=0, downloads=0, files=0, dirs=0, country="")
		#for user in self.rooms[msg.room].users:
			#client = self.FindClientFromUsername(user)
		#self.watching[msg.username] = username
		self.Send(client, newmsg)
				
	def UserInterests(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if client is None:
			return
		hates = []
		likes = []
		if msg.username in self.users:
			hates = self.users[msg.username].hates
		if msg.username in self.users:
			likes = self.users[msg.username].likes

		newmsg = UserInterests(username=msg.username, likes=likes, hates=hates)
		#newmsg.debug()
		self.Send(client, newmsg)
		
	def GlobalRecommendations(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if client is None:
			return
		likes = {}
		hates = {}
		for user in self.users.values():
			for like in user.likes:
				if like in likes:
					likes[like] += 1
				else:
					likes[like] = 1
		for user in self.users.values():
			for hate in user.hates:
				if hate in hates:
					hates[hate] += 1
				else:
					hates[hate] = 1
		newmsg = GlobalRecommendations(recommendations=likes, unrecommendations=hates)
		#newmsg.debug()
		self.Send(client, newmsg)
		
	def Recommendations(self, msg):
		# Returns recommendations based on the users likes and hates
		(client, username) = self.ClientAndUsername(msg)
		if client is None or username is None or username not in self.users:
			return
		
		mylikes =  self.users[username].likes
		myhates =  self.users[username].hates
		likes = {}
		hates = {}
		for user, data in self.users.items():
			# Don't weigh your own recommendations
			if data.username == username:
				continue

			weight = 0
			# Do the your likes outweigh the user's dislikes?
			for like in mylikes:
				if like in data.likes:
					weight += 1
				if like in data.hates:
					weight -= 1
			# Do your dislikes outweigh the user's likes?
			for hate in myhates:
				if hate in data.hates:
					weight += 1
				if hate in data.likes:
					weight -= 1
			if weight > 0:
				# Adding the user's likes and dislikes to the recommendations
				for user_like in data.likes:
					if user_like in likes:
						likes[user_like] += 1
					else:
						likes[user_like] = 1
				for user_hate in data.hates:
					if user_hate in hates:
						hates[user_hate] += 1
					else:
						hates[user_hate] = 1
						
		newmsg = Recommendations(recommendations=likes, unrecommendations=hates)
		#newmsg.debug()
		self.Send(client, newmsg)
		
	def ItemRecommendations(self, msg):
		# Returns recommendations based on the users likes and hates
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if client is None or username is None or username not in self.users:
			return
		thing = msg.thing
		mylikes =  self.users[username].likes
		myhates =  self.users[username].hates
		likes = {}
		hates = {}
		for user, data in self.users.items():
			# Don't weigh your own recommendations
			if data.username == username:
				continue

			weight = 0
			# Do the your likes outweigh the user's dislikes?
			if thing in data.likes:
				weight += 1
			if thing in data.hates:
				weight -= 1
			# Do your dislikes outweigh the user's likes?
			if weight > 0:
				# Adding the user's likes and dislikes to the recommendations
				for user_like in data.likes:
					if user_like in likes:
						likes[user_like] += 1
					else:
						likes[user_like] = 1
				for user_hate in data.hates:
					if user_hate in hates:
						hates[user_hate] += 1
					else:
						hates[user_hate] = 1
						
		newmsg = ItemRecommendations(msg.thing, recommendations=likes, unrecommendations=hates)
		newmsg.debug()
		self.Send(client, newmsg)
		
	def SimilarUsers(self, msg):
		# Returns recommendations based on the users likes and hates
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if client is None or username is None or username not in self.users:
			return
		
		mylikes =  self.users[username].likes
		myhates =  self.users[username].hates
		users = {}
		for user, data in self.users.items():
			# Don't weigh your own recommendations
			if data.username == username:
				continue

			weight = 0
			# Do the your likes outweigh the user's dislikes?
			for like in mylikes:
				if like in data.likes:
					weight += 1
				if like in data.hates:
					weight -= 1
			# Do your dislikes outweigh the user's likes?
			for hate in myhates:
				if hate in data.hates:
					weight += 1
				if hate in data.likes:
					weight -= 1
			if weight > 0:
				# Adding the user and it's weight
				users[user]=weight
				
		
		newmsg = SimilarUsers(users=users)
		#newmsg.debug()
		self.Send(client, newmsg)
		
	def ItemSimilarUsers(self, msg):
		# Returns recommendations based on the users likes and hates
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if client is None or username is None or username not in self.users:
			return
		thing = msg.thing
		users = {}
		for user, data in self.users.items():
			# Don't weigh your own recommendations
			if data.username == username:
				continue
			weight = 0
			# Do the your likes outweigh the user's dislikes?
			if thing in data.likes:
				weight += 1
			if thing in data.hates:
				weight -= 1

			if weight > 0:
				# Adding the user and it's weight
				users[user]=weight
	
		newmsg = SimilarUsers(users=users)
		#newmsg.debug()
		self.Send(client, newmsg)
		
	def RoomTickerState(self, msg):
		# Todo: 
		msg.debug()

				
	def RoomTickerSet(self, msg):
		username = self.ClientUsername(self.FindMsgOwner(msg))
		if username is None:
			return
		if msg.room in self.rooms:
			if msg.msg != "":
				self.rooms[msg.room].tickers[username] = msg.msg
			else:
				if username in self.rooms[msg.room].tickers[username]:
						del self.rooms[msg.room].tickers[username]
						
			for user in self.rooms[msg.room].users:
				if msg.msg == "":
					newmsg = RoomTickerRemove(room=msg.room, username=username)
				else:
					newmsg = RoomTickerAdd(room=msg.room, username=username, msg=msg.msg)
				lookup = self.FindClientFromUsername(user)
				self.Send(lookup, newmsg)

	def MessageUser(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)

		lookup = self.FindClientFromUsername(msg.username)

		if msg.username not in self.privatemessages:
			self.privatemessages[msg.username] = {}
			msgid = 1

		else:
			msgid = 1 + len(self.privatemessages[msg.username])
		message = self.privatemessages[msg.username][msgid]  = {"username": username, "timestamp": int(time.time()), "message": msg.msg}
		
		if lookup is None:
			# no such user connected
			log.addError("user %s not connected, cannot send message" % msg.username)
			return
		newmsg = MessageUser(msgid=msgid, username=message["username"], timestamp=message["timestamp"], msg=message["message"])
		self.Send(lookup, newmsg)
	
	def MessageAcked(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if username not in self.privatemessages:
			log.addError("no user %s" % username)
		else:
			if msg.msgid in self.privatemessages[username]:
				del self.privatemessages[username][msg.msgid]
				
	
	def SayChatroom(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		#if username in self.user_stats:
		lookup = self.FindClientFromUsername(msg.username)
		if msg.room not in self.rooms:
			# no such room
			return
		newmsg = SayChatroom(room=msg.room, username=username, msg=msg.msg)
		
		log.addChat(username=username, message=msg.msg, room=msg.room)
		# Send message to all connected users in room
		for user in self.rooms[msg.room].users:
			client = self.FindClientFromUsername(user)
			if client is not None: 
				self.Send(client, newmsg)
		
	def GetPeerAddress(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		#if username in self.user_stats:
		lookup = self.FindClientFromUsername(msg.username)
		
		if lookup is None:
			# no such user connected
			log.addLine( "user %s not connected, cannot send message" % msg.username)
			ip = "0.0.0.0"
			port = 0
		else:
			ip = self.clientmap[lookup]["ip"]
			port = self.clientmap[lookup]["peerport"]
		log.addLine("user '%s' is looking up '%s'(%s %s)" % (username, msg.username, ip, port) )
		newmsg= GetPeerAddress(username=msg.username, ip=ip, port=port)
		self.Send(client, newmsg)
			
	def SetStatus(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if username in self.users:
			self.users[username].setStatus(msg.status)
		newmsg= GetUserStatus(username=username, status=msg.status, privileged=bool(self.users[username].privileges))
		for client in self.clientmap:
			self.Send(client, newmsg)
			
	def GetUserStatus(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if msg.username in self.users:
			data = self.users[msg.username]
			newmsg = GetUserStatus(username=msg.username, status=data.status, privileged=bool(data.privileges))
			self.Send(client, newmsg)
			
	def GetUserStats(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if msg.username in self.users:
			data = self.users[msg.username]
			newmsg = GetUserStats(username = data.username,  speed=data.speed, downloads=data.downloads, files=data.files, dirs=data.dirs)
			self.Send(client, newmsg)
		
	def JoinRoom(self, msg):
		room = msg.room
		(client, username) = self.ClientAndUsername(msg)
		if username not in self.users:
			return
		if room not in self.rooms:
			if msg.private:
				self.rooms[room] = RoomData(name=room, users=[], private=True, owner=username, operators=[])
			else:
				self.rooms[room] = RoomData(name=room, users=[], private=False)
		else:
			if msg.private and self.rooms[room].private is False:
				server_msg = AdminMessage("The room '%s' is public, so you cannot create it as a private room" % room)
				self.Send(client, server_msg)

		if username not in self.rooms[room].users:
			self.UserJoinedRoom(room, username)
			self.rooms[room].users.append(username)
		stats = {}
		for user in self.rooms[room].users:
			if user in self.users:
				stats[user] = self.users[user]
		newmsg = JoinRoom(room=room, private=self.rooms[room].private, users=self.rooms[room].users, stats=stats, owner=self.rooms[room].owner, operators=self.rooms[room].operators)
		self.Send(client, newmsg)
		

	def LeaveRoom(self, msg):
		room = msg.room
		(client, username) = self.ClientAndUsername(msg)
		log.addLine("%s leaving %s" % (username, room))
		if room in self.rooms:
			if username in self.rooms[room].users:
				self.rooms[room].users.remove(username)
				self.Send(client, LeaveRoom(room=room))
				self.UserLeftRoom(room, username)
			if len(self.rooms[room].users) == 0:
				del self.rooms[room]
				
	def UserJoinedRoom(self, room, username):
		stats = self.users[username]
	
		newmsg = UserJoinedRoom(room=room, username=username, stats=stats)
		for user in self.rooms[room].users:
			client = self.FindClientFromUsername(user)
			if client is not None:
				self.Send(client, newmsg)
		
	def UserLeftRoom(self, room, username):
		newmsg = UserLeftRoom(room=room, username=username)

		if username in self.rooms[room].users:
			self.rooms[room].users.remove(username)
		for user in self.rooms[room].users:
			client = self.FindClientFromUsername(user)
			if client is not None:
				self.Send(client, newmsg)
				
	def PrivateRoomToggle(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if client is None or username is None:
			return
		
		if username in self.users:
			self.users[username].privaterooms = msg.enabled
			newmsg = PrivateRoomToggle(enabled=msg.enabled)
			self.Send(client, newmsg)
		
	def SharedFoldersFiles(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		
		self.users[username].setFiles(msg.files)
		self.users[username].setDirectories(msg.folders)
		
	def ServerPing(self, msg):
		self.Send(self.FindMsgOwner(msg), ServerPing())
		
	def RoomListRequested(self, msg):
		self.SendRoomlist(msg)
	
	def ResendPrivateMessages(self, username):
		if username not in self.privatemessages:
			return
		lookup = self.FindClientFromUsername(username)
		if lookup is None:
			# no such user connected
			log.addError("user %s not connected, cannot send message" % username)
			return
		for msgid, message in self.privatemessages[username].items():
			newmsg = MessageUser(msgid=msgid, username=message["username"], timestamp=message["timestamp"], msg=message["message"])
			self.Send(lookup, newmsg)

	def SendRoomlist(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		publicrooms = {}
		ownedrooms = {}
		privaterooms = {}
		operatorrooms = {}
		for room in self.rooms:
			
			if self.rooms[room].private is False:
				publicrooms[room] = self.rooms[room]
			elif self.rooms[room].private is True:
				privaterooms[room] = self.rooms[room]
				if self.rooms[room].owner == username:
					ownedrooms[room] = self.rooms[room]
				elif username in self.rooms[room].operators:
					operatorrooms[room] = self.rooms[room]
		print publicrooms, ownedrooms, privaterooms, operatorrooms
		newmsg = RoomList(rooms=publicrooms, ownedrooms=ownedrooms, privaterooms=privaterooms, operatorrooms=operatorrooms)
		self.Send(client, newmsg)
		
	def Login(self, msg):
		try:
			import hashlib
			m = hashlib.md5()
		except:
			import md5
			m = md5.new()
		m.update(msg.password)
		client = None
		for client in self.clientmap:
			if msg in self.clientmap[client]["messages"]:
				self.clientmap[client]["username"] = msg.username
				break
		for client in self.clientmap:
			print self.clientmap[client]["username"] 
				
		if client is None:
			log.addLine( "Login Ghost detected %s" % client)
			return
		cc = ""
		if geoip is not None:
			cc = geoip.country_code_by_addr(self.clientmap[client]["ip"])
			if cc is None:
				cc = ""
		relogged = False
		if msg.username not in self.users:
			self.users[msg.username] = UserData(username=msg.username, password=msg.password, client_version=msg.version, status=1, logged_in=True, privileges=0, last_connected_time=int(time.time()), country=cc)
			success = 1
			reason="Welcome, first time user, %s" % msg.username
			newmsg = Login(success=success, reason=reason,  ip=self.clientmap[client]["ip"], checksum=m.hexdigest())
		else:
			
			if self.users[msg.username].password != msg.password:
				#or m.hexdigest() != msg.checksum
				success = 0
				reason="INVALIDPASS"
				newmsg = Login(success=success, reason="INVALIDPASS", ip=self.clientmap[client]["ip"], checksum=m.hexdigest())
			else:
				if self.users[msg.username].logged_in:
				
					for oldclient in self.clientmap.keys():
						if oldclient is not client:
							if self.clientmap[oldclient]["username"] == msg.username:
								log.addLine( "User %s already logged in, disconnecting old connection" % msg.username)
								relogged = Relogged()
								self.Send(oldclient, relogged)
								self.CloseSocket(oldclient)
								relogged = True
				success = 1
				self.users[msg.username].client_version = msg.version
				self.users[msg.username].status=1
				self.users[msg.username].logged_in=True
				self.users[msg.username].country=cc

				self.users[msg.username].last_connected_time = int(time.time())
				newmsg = Login(success=success, reason="Welcome back, %s" % msg.username, ip=self.clientmap[client]["ip"], checksum=m.hexdigest())

		self.Send(client, newmsg)
		if not success:
			self.CloseSocket(client, reason=reason)
		
	def SetWaitPort(self, msg):
		(client, username) = self.ClientAndUsername(msg)
		if client is None:
			log.addLine( "Waitport Ghost detected %s" % client)
			return
		self.clientmap[client]["peerport"] = msg.port
		# Send roomlist (since we have a working client, here)
		self.SendRoomlist(msg)
		# Send offline private messages
		self.ResendPrivateMessages(username)
		# Send list of privileged users
		self.Privileges.PrivilegedUsers(client)


	def ChangePassword(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		
		if username in self.users:
			data = self.users[username]
			if data.logged_in:
				data.setPassword(msg.password)
				newmsg = ChangePassword(password=msg.password)
				self.Send(client, newmsg)
				
	def CheckPrivileges(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if username in self.users:
			data = self.users[username]
			if data.logged_in:
				newmsg = CheckPrivileges(seconds=data.privileges)
				self.Send(client, newmsg)
			
	def GivePrivileges(self, msg):
		client = self.FindMsgOwner(msg)
		username = self.ClientUsername(client)
		if username in self.users:
			self.Privileges.GivePrivileges(username, msg.days, msg.username)


		
	def OnNetworkEvent(self,msgs):
		for i in msgs:
			if i.__class__ in self.events:
				try:
					if i.__class__ is not str:
						user = self.ClientUsername(self.FindMsgOwner(i))
						log.addLine("Received\t%(messagetype)s\t\tfrom %(user)s" % {"user": user, "messagetype": str(i.__class__).rsplit(".")[1]} )
						if user is None and i.__class__ is not Login:
							log.addError("WARNING: Ghost Detected: %s" % self.FindMsgOwner(i))
						
					self.events[i.__class__](i)
				except Exception, e:
					log.addError( i.__class__)
					displayTraceback(sys.exc_info()[2])
			else:

				log.addParsingError("No handler for class %s %s" % (i.__class__, i) )
				i.debug()
				
	def FindRoomsUserIsIn(self, username):
		rooms = []
		for room in self.rooms:
			if username in self.rooms[room].users:
				rooms.append(room)
		return rooms
		
				
	def serve(self):
		
		self.inputs = [self.server]
		self.outputs = []

		running = 1

		while running:
			try:
				inputready,outputready,exceptready = select.select(self.inputs, self.outputs, [], 0.5)
			except select.error, e:
				log.addError( "Select: %s" % e)
				break
			except socket.error, e:
				log.addError( "Socket: %s" % e)
			except IOError, e:
				if e.errno == errno.EPIPE:
					log.addError( "Broken pipe: %s" % e)
			except Exception,e:
				log.addError( "Broken pipe: %s" % e)
			for sockets in inputready[:]:
				address = None
				client = None
				if sockets == self.server:
					# handle the server socket
					client, address = self.server.accept()
					log.addLine('New connection %d from %s' % (client.fileno(), address))
					msgs = self.Receive(client, address)
					
					inputready.remove(sockets)
					if client in self.clientmap:
						if client not in self.inputs:
							self.inputs.append(client)
				else:
					msgs = self.Receive(sockets)
					inputready.remove(sockets)
				if msgs is not None:
					self.OnNetworkEvent(msgs)
				else:
					if sockets in self.inputs:
						self.inputs.remove(sockets)

			time.sleep(0.1)
		self.server.close()
		
		
	def Receive(self, client, address=None):
		
		
		if client not in self.clientmap:
			if address is not None:
				self.clientmap[client] = {"ip":address[0], "port": address[1], "messages":[], "username":None, "peerport": 0, "buffer":""}
				msgs, msgBuffer = self.ReceiveData( client)
				
			else:
				log.addError("unknown address for: %s" % client)
		else:
			msgs, msgBuffer = self.ReceiveData(client, self.clientmap[client]["buffer"])
		if msgs is None or msgBuffer is None:
			return
		self.clientmap[client]["buffer"] = msgBuffer
		self.clientmap[client]["messages"] += msgs
		return msgs
		
	def ReceiveData(self,  channel, msgBuffer=""):
		try:
			buffer = channel.recv(1024)
		except:
			log.addError("Error receiving data from this socket: %s" % channel)
			buffer = None
		if not buffer:
			self.CloseSocket(channel)
			
			return None, None
		msgBuffer+=buffer
		#msgBuffer += channel.recv(struct.unpack("<i", msgBuffer[:4])[0])
		msgs = []
		# Server messages are 8 bytes or greater in length
		while len(msgBuffer) >= 8:
			msgsize, msgtype = struct.unpack("<ii", msgBuffer[:8])
			if msgsize + 4 > len(msgBuffer):
				break
			elif msgtype in self.Messages.serverclasses:
				msg = self.Messages.serverclasses[msgtype]()
				try:
					msg.parseNetworkMessage(msgBuffer[8:msgsize+4])
				except Exception, error:
					log.addParsingError("%s: %s" % (msg.__class__, error) )
					
				else:
					msgs.append(msg)
			else:
				log.addError(_("Server message type %(type)i size %(size)i contents %(msgBuffer)s unknown") %{'type':msgtype, 'size':msgsize-4, 'msgBuffer':msgBuffer[8:msgsize+4].__repr__()})
			msgBuffer = msgBuffer[msgsize+4:]
		
		return msgs, msgBuffer
		
	def Send(self, client, msg):
		if client is None:
			log.addError("Nonexistant socket, giving up sending: %(messagetype)s" % {"messagetype": str(msg.__class__).rsplit(".")[1] })
			return
		newmsg = msg.makeNetworkMessage()
		packedMessage = struct.pack("<ii", len(newmsg)+4, self.Messages.servercodes[msg.__class__]) + newmsg
		log.addLine( "Sending\t%(messagetype)s\t\tto %(user)s" % {"messagetype": str(msg.__class__).rsplit(".")[1], "user": self.ClientUsername(client) } )
		self.sendData(client, packedMessage)
		
	def sendData(self, channel, *args):
		if channel not in self.clientmap.keys() + [self.server]:
			log.addError("this socket may be closed, could fail disasterously %s"  % channel)
		try:
			channel.send(*args)
		except socket.error,e:
			log.addError("Sending failed: %s socket closed: %s" % (e, channel ))
			#displayTraceback(sys.exc_info()[2])
		except IOError, e:
			if e.errno == errno.EPIPE:
				log.addError("Broken pipe: %s, %s" % (e, channel ))
				self.CloseSocket(channel)
				
	def CloseSocket(self, client, reason=None):
		if client in self.clientmap:
			username = self.clientmap[client]["username"]
			if reason is not None:
				if reason != "INVALIDPASS":
					log.addLine("Client disconnected: %s" % username)
				else:
					log.addLine("Client with invalid password for user '%s' disconnected" % username)
				
			del self.clientmap[client]
			client.close()
			if client in self.outputs:
				self.outputs.remove(client)
			if client in self.inputs:
				self.inputs.remove(client)
			self.DisconnectedUser(username)
			
	def DisconnectedUser(self, username):
		rooms = self.FindRoomsUserIsIn(username)
		for room in rooms:
			self.UserLeftRoom(room, username)
		if username in self.users:
			data = self.users[username]
			data.tickers = []
			data.likes = []
			data.hates = []
			data.logged_in=False
			data.status=0
			newmsg = GetUserStatus(username, data.status, bool(data.privileges))
			for user in data.IsWatchedBy:
				if user == username:
					continue
				lookup = self.FindClientFromUsername(user)
				if lookup is not None:
					self.Send(lookup, newmsg)
	def sighandler(self, signum, frame):
		# Close the server
		log.addLine('Shutting down server...')
		# Close existing client sockets

		shutdown_message = AdminMessage('Message from Server Administration: We are now shutting down the server for a unknown length of time...')
		for user, data in self.users.items():
			if data.logged_in:
				lookup = self.FindClientFromUsername(user)
				try:
					self.Send(lookup, shutdown_message)
				except:
					pass
				lookup.close()
				data.logged_in=False
		for o in self.outputs:
			o.close()
		self.writeData()
		#self.writeUsers()
		#self.writePrivateChats()
		self.server.close()
		sys.exit()
		
def usage():
	print _("""SoulServer is a Soulseek-compatible server.
Usage: soulserver [OPTION]...
  -c file, --config=file      Use non-default configuration file
  -p port, --port=[port]      Use non-default port
  -v,      --version          Display version and exit
  -h,      --help             Display this help and exit
""")
def run():
	try:
		opts, args = getopt.getopt(sys.argv[1:], "hc:p:v", ["help", "config=", "port=", "version"])
	except getopt.GetoptError:
		#print help information and exit:
		usage()
		sys.exit(2)
	PORT = 2242
	for o, a in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit()
		if o in ("-c", "--config"):
			config = a
		if o in ("-p", "--port"):
			try:
				PORT = int(a)
			except:
				log.addError("Invalid port %s inputed, using default, %s" %(a, PORT))
		if o in ("-v", "--version"):
			print ("SoulServer version: %s" %VERSION)
			sys.exit()
	SoulServer(port=PORT).serve()
	
if __name__ == "__main__":
	try:
		run()
	except SystemExit:
		raise
	except: # BaseException doesn't exist in python2.4
		import traceback
		traceback.print_exc()
